package com.zxx.dis.lock;

import com.zxx.dis.core.AbstractDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;


@Slf4j
public class RedisDistributedLock extends AbstractDistributedLock {

    private final StringRedisTemplate redisTemplate;
    private final String threadId;
    private static final String LOCK_PREFIX = "distributed_lock:";
    private final ScheduledExecutorService watchdogExecutor;
    private ScheduledFuture<?> renewalFuture;

    private static final String LOCK_SCRIPT =
            "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
                    "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                    "   redis.call('expire', KEYS[1], ARGV[2]) " +
                    "   return 1 " +
                    "else " +
                    "   return 0 " +
                    "end";
    // Redis Lua脚本: 释放锁
    private static final String UNLOCK_SCRIPT =
            "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " +
                    "   return 0 " +
                    "else " +
                    "   local count = redis.call('hincrby', KEYS[1], ARGV[1], -1) " +
                    "   if count == 0 then " +
                    "       redis.call('del', KEYS[1]) " +
                    "   end " +
                    "   return 1 " +
                    "end";

    public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockKey,
                                long expireTime, TimeUnit timeUnit, String uuid) {
        super(lockKey, expireTime, timeUnit);
        this.redisTemplate = redisTemplate;
        this.threadId = Thread.currentThread().getId() + ":" + uuid;
        this.watchdogExecutor = new ScheduledThreadPoolExecutor(1, r -> {
            Thread thread = new Thread(r, "Lock-Watchdog-" + lockKey);
            thread.setDaemon(true);
            return thread;
        });
    }

    public void lock() {
        tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<>(LOCK_SCRIPT, Boolean.class);

        while (!redisTemplate.execute(
                lockScript,
                Arrays.asList(getLockKey()),
                threadId,
                String.valueOf(expireTime)
        )) {
            Thread.sleep(100);
        }
        startWatchdog();
        return false;
    }

    @Override
    public void unlock() {
        try {
            DefaultRedisScript<Boolean> unlockScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Boolean.class);
            Boolean isDeleted = redisTemplate.execute(unlockScript,
                    Arrays.asList(getLockKey()),
                    threadId);
            if (!isDeleted) {
                throw new IllegalMonitorStateException("尝试释放未持有的锁");
            }
        } finally {
            stopWatchdog();
        }


    }

    @Override
    public Condition newCondition() {
        return null;
    }

    private String getLockKey() {
        return LOCK_PREFIX + lockKey;
    }

    private void startWatchdog() {
        renewalFuture = watchdogExecutor.scheduleAtFixedRate(() -> {
            String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
                    "redis.call('expire', KEYS[1], ARGV[2]); " +
                    "return 1; " +
                    "else " +
                    "return 0; " +
                    "end";

            try {
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                Long executeRes = redisTemplate.execute(redisScript,
                        Arrays.asList(getLockKey()),
                        threadId,
                        String.valueOf(expireTime));
                System.out.println(executeRes);
            } catch (Exception e) {
                log.error("Failed to renew lock", e);
            }
        }, expireTime / 3, expireTime / 3, timeUnit);
    }

    private void stopWatchdog() {
        if (renewalFuture != null) {
            renewalFuture.cancel(false);
        }
    }

}
